/** biome-ignore-all lint/suspicious/noExplicitAny: This use requires any */
"use client";

import * as React from "react";

import { type CreatePlateEditorOptions } from "platejs/react";

import { getCommentKey, getDraftCommentKey } from "@platejs/comment";
import { CommentPlugin, useCommentId } from "@platejs/comment/react";
import {
  differenceInDays,
  differenceInHours,
  differenceInMinutes,
  format,
} from "date-fns";
import {
  ArrowUpIcon,
  CheckIcon,
  MoreHorizontalIcon,
  PencilIcon,
  TrashIcon,
  XIcon,
} from "lucide-react";
import { type Value, KEYS, nanoid, NodeApi } from "platejs";
import {
  Plate,
  useEditorPlugin,
  useEditorRef,
  usePlateEditor,
  usePluginOption,
} from "platejs/react";

import { BasicMarksKit } from "@/components/plate/plugins/basic-marks-kit";
import {
  type TDiscussion,
  discussionPlugin,
} from "@/components/plate/plugins/discussion-kit";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/components/plate/ui/avatar";
import { Button } from "@/components/plate/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/plate/ui/dropdown-menu";
import { cn } from "@/lib/utils";

import { Editor, EditorContainer } from "./editor";

export interface TComment {
  id: string;
  contentRich: Value;
  createdAt: Date;
  discussionId: string;
  isEdited: boolean;
  userId: string;
}

export function Comment(props: {
  comment: TComment;
  discussionLength: number;
  editingId: string | null;
  index: number;
  setEditingId: React.Dispatch&lt;React.SetStateAction&lt;string | null&gt;&gt;;
  documentContent?: string;
  showDocumentContent?: boolean;
  onEditorClick?: () =&gt; void;
}) {
  const {
    comment,
    discussionLength,
    documentContent,
    editingId,
    index,
    setEditingId,
    showDocumentContent = false,
    onEditorClick,
  } = props;

  const editor = useEditorRef();
  const userInfo = usePluginOption(discussionPlugin, "user", comment.userId);
  const currentUserId = usePluginOption(discussionPlugin, "currentUserId");

  const resolveDiscussion = async (id: string) =&gt; {
    const updatedDiscussions = editor
      .getOption(discussionPlugin, "discussions")
      .map((discussion) =&gt; {
        if (discussion.id === id) {
          return { ...discussion, isResolved: true };
        }
        return discussion;
      });
    editor.setOption(discussionPlugin, "discussions", updatedDiscussions);
  };

  const removeDiscussion = async (id: string) =&gt; {
    const updatedDiscussions = editor
      .getOption(discussionPlugin, "discussions")
      .filter((discussion) =&gt; discussion.id !== id);
    editor.setOption(discussionPlugin, "discussions", updatedDiscussions);
  };

  const updateComment = async (input: {
    id: string;
    contentRich: Value;
    discussionId: string;
    isEdited: boolean;
  }) =&gt; {
    const updatedDiscussions = editor
      .getOption(discussionPlugin, "discussions")
      .map((discussion) =&gt; {
        if (discussion.id === input.discussionId) {
          const updatedComments = discussion.comments.map((comment) =&gt; {
            if (comment.id === input.id) {
              return {
                ...comment,
                contentRich: input.contentRich,
                isEdited: true,
                updatedAt: new Date(),
              };
            }
            return comment;
          });
          return { ...discussion, comments: updatedComments };
        }
        return discussion;
      });
    editor.setOption(discussionPlugin, "discussions", updatedDiscussions);
  };

  const { tf } = useEditorPlugin(CommentPlugin);

  // Replace to your own backend or refer to potion
  const isMyComment = currentUserId === comment.userId;

  const initialValue = comment.contentRich;

  const commentEditor = useCommentEditor(
    {
      id: comment.id,
      value: initialValue,
    },
    [initialValue],
  );

  const onCancel = () =&gt; {
    setEditingId(null);
    commentEditor.tf.replaceNodes(initialValue, {
      at: [],
      children: true,
    });
  };

  const onSave = () =&gt; {
    void updateComment({
      id: comment.id,
      contentRich: commentEditor.children,
      discussionId: comment.discussionId,
      isEdited: true,
    });
    setEditingId(null);
  };

  const onResolveComment = () =&gt; {
    void resolveDiscussion(comment.discussionId);
    tf.comment.unsetMark({ id: comment.discussionId });
  };

  const isFirst = index === 0;
  const isLast = index === discussionLength - 1;
  const isEditing = editingId && editingId === comment.id;

  const [hovering, setHovering] = React.useState(false);
  const [dropdownOpen, setDropdownOpen] = React.useState(false);

  return (
    <div> setHovering(true)}
      onMouseLeave={() =&gt; setHovering(false)}
    &gt;
      <div>
        &lt;Avatar className="size-5"&gt;
          &lt;AvatarImage alt={userInfo?.name} src={userInfo?.avatarUrl} /&gt;
          &lt;AvatarFallback&gt;{userInfo?.name?.[0]}&lt;/AvatarFallback&gt;
        &lt;/Avatar&gt;
        <h4>
          {/* Replace to your own backend or refer to potion */}
          {userInfo?.name}
        </h4>

        <div>
          <span>
            {formatCommentDate(new Date(comment.createdAt))}
          </span>
          {comment.isEdited && <span>(edited)</span>}
        </div>

        {isMyComment && (hovering || dropdownOpen) && (
          <div>
            {index === 0 && (
              &lt;Button
                variant="ghost"
                className="h-6 p-1 text-muted-foreground"
                onClick={onResolveComment}
                type="button"
              &gt;
                &lt;CheckIcon className="size-4" /&gt;
              &lt;/Button&gt;
            )}

            &lt;CommentMoreDropdown
              onCloseAutoFocus={() =&gt; {
                setTimeout(() =&gt; {
                  commentEditor.tf.focus({ edge: "endEditor" });
                }, 0);
              }}
              onRemoveComment={() =&gt; {
                if (discussionLength === 1) {
                  tf.comment.unsetMark({ id: comment.discussionId });
                  void removeDiscussion(comment.discussionId);
                }
              }}
              comment={comment}
              dropdownOpen={dropdownOpen}
              setDropdownOpen={setDropdownOpen}
              setEditingId={setEditingId}
            /&gt;
          </div>
        )}
      </div>

      {isFirst && showDocumentContent && (
        <div>
          {discussionLength &gt; 1 && (
            <div />
          )}
          <div />
          {documentContent && <div>{documentContent}</div>}
        </div>
      )}

      <div>
        {!isLast && (
          <div />
        )}
        &lt;Plate readOnly={!isEditing} editor={commentEditor}&gt;
          &lt;EditorContainer variant="comment"&gt;
            &lt;Editor
              variant="comment"
              className="w-auto grow"
              onClick={() =&gt; onEditorClick?.()}
            /&gt;

            {isEditing && (
              <div>
                &lt;Button
                  size="icon"
                  variant="ghost"
                  className="size-[28px]"
                  onClick={(e: React.MouseEvent&lt;HTMLButtonElement&gt;) =&gt; {
                    e.stopPropagation();
                    void onCancel();
                  }}
                &gt;
                  <div>
                    &lt;XIcon className="size-3 stroke-[3px] text-background" /&gt;
                  </div>
                &lt;/Button&gt;

                &lt;Button
                  size="icon"
                  variant="ghost"
                  onClick={(e: React.MouseEvent&lt;HTMLButtonElement&gt;) =&gt; {
                    e.stopPropagation();
                    void onSave();
                  }}
                &gt;
                  <div>
                    &lt;CheckIcon className="size-3 stroke-[3px] text-background" /&gt;
                  </div>
                &lt;/Button&gt;
              </div>
            )}
          &lt;/EditorContainer&gt;
        &lt;/Plate&gt;
      </div>
    </div>
  );
}

function CommentMoreDropdown(props: {
  comment: TComment;
  dropdownOpen: boolean;
  setDropdownOpen: React.Dispatch&lt;React.SetStateAction&lt;boolean&gt;&gt;;
  setEditingId: React.Dispatch&lt;React.SetStateAction&lt;string | null&gt;&gt;;
  onCloseAutoFocus?: () =&gt; void;
  onRemoveComment?: () =&gt; void;
}) {
  const {
    comment,
    dropdownOpen,
    setDropdownOpen,
    setEditingId,
    onCloseAutoFocus,
    onRemoveComment,
  } = props;

  const editor = useEditorRef();

  const selectedEditCommentRef = React.useRef&lt;boolean&gt;(false);

  const onDeleteComment = React.useCallback(() =&gt; {
    if (!comment.id)
      return alert("You are operating too quickly, please try again later.");

    // Find and update the discussion
    const updatedDiscussions = editor
      .getOption(discussionPlugin, "discussions")
      .map((discussion) =&gt; {
        if (discussion.id !== comment.discussionId) {
          return discussion;
        }

        const commentIndex = discussion.comments.findIndex(
          (c) =&gt; c.id === comment.id,
        );
        if (commentIndex === -1) {
          return discussion;
        }

        return {
          ...discussion,
          comments: [
            ...discussion.comments.slice(0, commentIndex),
            ...discussion.comments.slice(commentIndex + 1),
          ],
        };
      });

    // Save back to session storage
    editor.setOption(discussionPlugin, "discussions", updatedDiscussions);
    onRemoveComment?.();
  }, [comment.discussionId, comment.id, editor, onRemoveComment]);

  const onEditComment = React.useCallback(() =&gt; {
    selectedEditCommentRef.current = true;

    if (!comment.id)
      return alert("You are operating too quickly, please try again later.");

    setEditingId(comment.id);
  }, [comment.id, setEditingId]);

  return (
    &lt;DropdownMenu
      open={dropdownOpen}
      onOpenChange={setDropdownOpen}
      modal={false}
    &gt;
      &lt;DropdownMenuTrigger asChild onClick={(e) =&gt; e.stopPropagation()}&gt;
        &lt;Button variant="ghost" className={cn("h-6 p-1 text-muted-foreground")}&gt;
          &lt;MoreHorizontalIcon className="size-4" /&gt;
        &lt;/Button&gt;
      &lt;/DropdownMenuTrigger&gt;
      &lt;DropdownMenuContent
        className="w-48"
        onCloseAutoFocus={(e) =&gt; {
          if (selectedEditCommentRef.current) {
            onCloseAutoFocus?.();
            selectedEditCommentRef.current = false;
          }

          return e.preventDefault();
        }}
      &gt;
        &lt;DropdownMenuGroup&gt;
          &lt;DropdownMenuItem onClick={onEditComment}&gt;
            &lt;PencilIcon className="size-4" /&gt;
            Edit comment
          &lt;/DropdownMenuItem&gt;
          &lt;DropdownMenuItem onClick={onDeleteComment}&gt;
            &lt;TrashIcon className="size-4" /&gt;
            Delete comment
          &lt;/DropdownMenuItem&gt;
        &lt;/DropdownMenuGroup&gt;
      &lt;/DropdownMenuContent&gt;
    &lt;/DropdownMenu&gt;
  );
}

const useCommentEditor = (
  options: Omit&lt;CreatePlateEditorOptions, "plugins"&gt; = {},
  deps: any[] = [],
) =&gt; {
  const commentEditor = usePlateEditor(
    {
      id: "comment",
      plugins: BasicMarksKit,
      value: [],
      ...options,
    },
    deps,
  );

  return commentEditor;
};

export function CommentCreateForm({
  autoFocus = false,
  className,
  discussionId: discussionIdProp,
  focusOnMount = false,
}: {
  autoFocus?: boolean;
  className?: string;
  discussionId?: string;
  focusOnMount?: boolean;
}) {
  const discussions = usePluginOption(discussionPlugin, "discussions");

  const editor = useEditorRef();
  const commentId = useCommentId();
  const discussionId = discussionIdProp ?? commentId;

  const userInfo = usePluginOption(discussionPlugin, "currentUser");
  const [commentValue, setCommentValue] = React.useState&lt;Value | undefined&gt;();
  const commentContent = React.useMemo(
    () =&gt;
      commentValue
        ? NodeApi.string({ children: commentValue, type: KEYS.p })
        : "",
    [commentValue],
  );
  const commentEditor = useCommentEditor({}, []);

  React.useEffect(() =&gt; {
    if (commentEditor && focusOnMount) {
      commentEditor.tf.focus();
    }
  }, [commentEditor, focusOnMount]);

  const onAddComment = React.useCallback(async () =&gt; {
    if (!commentValue) return;

    commentEditor.tf.reset();

    if (discussionId) {
      // Get existing discussion
      const discussion = discussions.find((d) =&gt; d.id === discussionId);
      if (!discussion) {
        // Mock creating suggestion
        const newDiscussion: TDiscussion = {
          id: discussionId,
          comments: [
            {
              id: nanoid(),
              contentRich: commentValue,
              createdAt: new Date(),
              discussionId,
              isEdited: false,
              userId: editor.getOption(discussionPlugin, "currentUserId"),
            },
          ],
          createdAt: new Date(),
          isResolved: false,
          userId: editor.getOption(discussionPlugin, "currentUserId"),
        };

        editor.setOption(discussionPlugin, "discussions", [
          ...discussions,
          newDiscussion,
        ]);
        return;
      }

      // Create reply comment
      const comment: TComment = {
        id: nanoid(),
        contentRich: commentValue,
        createdAt: new Date(),
        discussionId,
        isEdited: false,
        userId: editor.getOption(discussionPlugin, "currentUserId"),
      };

      // Add reply to discussion comments
      const updatedDiscussion = {
        ...discussion,
        comments: [...discussion.comments, comment],
      };

      // Filter out old discussion and add updated one
      const updatedDiscussions = discussions
        .filter((d) =&gt; d.id !== discussionId)
        .concat(updatedDiscussion);

      editor.setOption(discussionPlugin, "discussions", updatedDiscussions);

      return;
    }

    const commentsNodeEntry = editor
      .getApi(CommentPlugin)
      .comment.nodes({ at: [], isDraft: true });

    if (commentsNodeEntry.length === 0) return;

    const documentContent = commentsNodeEntry
      .map(([node]) =&gt; node.text)
      .join("");

    const _discussionId = nanoid();
    // Mock creating new discussion
    const newDiscussion: TDiscussion = {
      id: _discussionId,
      comments: [
        {
          id: nanoid(),
          contentRich: commentValue,
          createdAt: new Date(),
          discussionId: _discussionId,
          isEdited: false,
          userId: editor.getOption(discussionPlugin, "currentUserId"),
        },
      ],
      createdAt: new Date(),
      documentContent,
      isResolved: false,
      userId: editor.getOption(discussionPlugin, "currentUserId"),
    };

    editor.setOption(discussionPlugin, "discussions", [
      ...discussions,
      newDiscussion,
    ]);

    const id = newDiscussion.id;

    commentsNodeEntry.forEach(([, path]) =&gt; {
      editor.tf.setNodes(
        {
          [getCommentKey(id)]: true,
        },
        { at: path, split: true },
      );
      editor.tf.unsetNodes([getDraftCommentKey()], { at: path });
    });
  }, [commentValue, commentEditor.tf, discussionId, editor, discussions]);

  return (
    <div>
      <div>
        {/* Replace to your own backend or refer to potion */}
        &lt;Avatar className="size-5"&gt;
          &lt;AvatarImage alt={userInfo?.name} src={userInfo?.avatarUrl} /&gt;
          &lt;AvatarFallback&gt;{userInfo?.name?.[0]}&lt;/AvatarFallback&gt;
        &lt;/Avatar&gt;
      </div>

      <div>
        &lt;Plate
          onChange={({ value }) =&gt; {
            setCommentValue(value);
          }}
          editor={commentEditor}
        &gt;
          &lt;EditorContainer variant="comment"&gt;
            &lt;Editor
              variant="comment"
              className="min-h-[25px] grow pr-8 pt-0.5"
              onKeyDown={(e) =&gt; {
                if (e.key === "Enter" && !e.shiftKey) {
                  e.preventDefault();
                  onAddComment();
                }
              }}
              placeholder="Reply..."
              autoComplete="off"
              autoFocus={autoFocus}
            /&gt;

            &lt;Button
              size="icon"
              variant="ghost"
              className="absolute bottom-0.5 right-0.5 ml-auto size-6 shrink-0"
              disabled={commentContent.trim().length === 0}
              onClick={(e) =&gt; {
                e.stopPropagation();
                onAddComment();
              }}
            &gt;
              <div>
                &lt;ArrowUpIcon /&gt;
              </div>
            &lt;/Button&gt;
          &lt;/EditorContainer&gt;
        &lt;/Plate&gt;
      </div>
    </div>
  );
}

export const formatCommentDate = (date: Date) =&gt; {
  const now = new Date();
  const diffMinutes = differenceInMinutes(now, date);
  const diffHours = differenceInHours(now, date);
  const diffDays = differenceInDays(now, date);

  if (diffMinutes &lt; 60) {
    return `${diffMinutes}m`;
  }
  if (diffHours &lt; 24) {
    return `${diffHours}h`;
  }
  if (diffDays &lt; 2) {
    return `${diffDays}d`;
  }

  return format(date, "MM/dd/yyyy");
};
